﻿#pragma once

#include <set>
#include <memory>
#include "SampleData.h"

/*
整群处理：可以选择字段，可以选择以哪个字段开始的字符来归并
  选择样框 （样框管理，修改结构， 整群处理）
  3种抽样统计方式，
	随机 样本量？ 最大相对误差 = 1- 设计精度 哪里有设计精度
	等距
	分层：还希望支持多层的方式
  抽样，样本管理
  数据导入/导出（可以利用现有功能） 整群处理
  汇总/推算 需要了解一下
  其他的一些操作： 指标唯一性检查； 清空/压缩数据库等；

增加抽样方法
修改bug：excel导入时，开始的0消失
主要是奈曼分配这个，比例是如何设置的
添加一个多层抽样*/
// 目标调查指标
// 整群处理后的结果，应该只能用在
namespace sample {
	class SampleDesign ;
	typedef shared_ptr<SampleDesign> SampleDesignPtr;

	// 使用这个类来记住每次做的采样结果；  一个采样设计可以多次采样，多次采样后，可以保存结果
	// 暂时不支持总体在抽样后有变化，如果有变化，上层应该处理好（例如重建总体对象）
	// 注意，如果一个抽样设计修改了，除非没有被使用，否则应该新建一个抽样设计
	struct SampleResult {
		SampleDesign*					sampleDesign = nullptr;
		unsigned						sampleNo = 0;	// 抽样编号; 和SampleManager::sampleResults_的key是相同的
		// 抽样过程中进行的选择（主要是记录通过随机数得到的记录选择）; 对于分层抽样，按层记录
		// key: layer1_layer2
		std::map<string, set<unsigned> > results;		// 编号从0开始	
	};

	class SampleManager
	{
	public:
	  	static SampleManager& getInstance() {
			static SampleManager instance;
			return instance;
		}

		/// @brief 数据落盘，是否要考虑，每次有操作，该落盘的都落盘
		void save();
		/// @brief 启动的时候加载数据
		void load();

		void addSampleDesign(SampleDesignPtr sd) { sampleDesigns_[++curSampleDesignNo_] = sd; }

		SampleFrame& addSampleFrame(const string& name) { return sampleFrames_[name]; }
		SampleFrame* getSampleFrame(const string& name) { 
			auto it = sampleFrames_.find(name);
			return it == sampleFrames_.end() ? nullptr : &(it->second);
		}
		// 如果是空的，会自动给一个名字
		SampleDesignPtr getSampleDesign(unsigned no) {
			auto it = sampleDesigns_.find(no);
			return it == sampleDesigns_.end() ? nullptr : it->second;
		}
		
		SampleResult& addSampleResult(const SampleResult& result) { 
			auto& res = sampleResults_[++curSampleNo_];
			res = result;
			res.sampleNo = curSampleNo_;
			return res;
		}
		SampleResult& getSampleResult(unsigned no) { return sampleResults_[no]; }



	private:
		std::map<std::string, SampleFrame>		sampleFrames_;	// 样框数据

		unsigned								curSampleDesignNo_ = 0; // 当前采样设计编号		
		std::map<unsigned, SampleDesignPtr>		sampleDesigns_;	// 所有的抽样设计;  会自动命名  1号抽样设计。。。

		unsigned								curSampleNo_ = 0;	// 当前采样编号
		std::map<unsigned, SampleResult>		sampleResults_; // 所有的抽样结果  ，会自动命名  xxx号样本[抽样设计名,抽样方法,抽样数]
	};

	class SampleDesign {
	public:
		SampleDesign(SampleFrame& p) : sampleFrame_(p) {}

		virtual SampleResult* sample() = 0;	// 进行采样计算; 采样的结果会保存在SampleManager::sampleResults_中

		// 设置相对误差，同时计算样本总量
		void setRelativeError(double error) { 
			relativeError_ = error; 
		}
		/// <summary>
		/// 计算样本大小，需要先设置好相对误差
		/// </summary>
		/// <returns></returns>
		virtual bool calcSampleSize();
		/// <summary>
		/// 计算相对误差，想要先设置好样本大小
		/// </summary>
		/// <returns></returns>
		virtual bool calcRelativeError();
		void setSampleSize(unsigned size) { sampleSize_ = size; }
		void setMetric(const std::string& metric) {
			metric_ = metric;
		}

	protected:
		SampleFrame& 	sampleFrame_;	// 每个设计建立在一个样框/总体（Population）下
		std::string		metric_;		// 使用的指标名字

		unsigned		sampleSize_ = 0;		// 样本总量   样本总量和相对误差有换算关系   0表示未设置; 可以手工设置，或者根据误差计算出来
		double			relativeError_ = 0.03;	// 最大相对误差   0表示未设置  (0 ~ 1.0)
	};

	// 随机采样
	class RandomSample: public SampleDesign{
	public:
		RandomSample(SampleFrame& p) : SampleDesign(p) {}
		virtual SampleResult* sample();
	};

	/// 系统/等距采样
	class SystemSample : public SampleDesign
	{
	public:
		SystemSample(SampleFrame& p) : SampleDesign(p) {}
		~SystemSample() {};

		enum SortType {
			Linear = 0,				// r + (j-1)k
			Circular = 1,			// (r + jk) % n
		};

		virtual SampleResult* sample();
	private:
		SortType	sortType_ = Linear;
		std::string sortMetric_;	/// 排序指标  可能和抽样指标不同
	};


	
	// 分层采样; 根据指标分层，可以选择样本分配方式，确定每层的样本； 也可以根据需要的精度来调整各层需要的样本量 （精度 + 样本分配方式）
	class StratifiedSample : public SampleDesign {
	public:
		enum StratifiedType {
			StratifiedBy = 0,		// 样框平均数
			StratifiedByMetric = 1,	// 指标累计平均数， 是否是将指标值累加，按数值分层
		};
		enum SampleConfigType {
			SampleConfigTypeEven				= 0,	// 平均分配	
			SampleConfigTypeSampleFrameScale	= 1,	// 样框数等比分配
			SampleConfigTypeMetricScale			= 2,	// 调查指标值等比分配
			SampleConfigTypeNeman				= 3,	// 奈曼分配
		};

		struct LayerInfo {
			vector<unsigned>	populationNos;	// 如果使用了字符串指标的分层，每层具体的元素（行号）
			unsigned	 		populationSize = 0;	// 如果使用了数量型指标分层，每层的总体数量，暂时不能手工修改，否则可能导致数量不一致的问题

			// 基类的sampleSize_是样本总量
			unsigned			sampleSize = 0;	// 用户可以调整每层的样本数量   否则就是根据样本总量或者要求的误差计算出来的

			double		error = 0.03;				// 最大相对误差
			unsigned	maxSize;
			unsigned	minSize;
		};
		double layerVariance(const LayerInfo& layer) const;
		double layerStdDev(const LayerInfo& layer) const;
		unsigned layerPopulationSize(const LayerInfo& layer) const;

	public:
		StratifiedSample(SampleFrame& p) : SampleDesign(p) {}

		virtual SampleResult* sample();

		/// 设置分层信息; 参数layerMetricSize为0表示全部字符，否则取前几个字符
		/// 如果使用字符串类型分层，则layerCount_无效； 如果使用数值型类型分层，则layerMetricSize_无效
		void setLayeringInfo(const std::string& layerMetric, unsigned layerMetricSize = 0, unsigned	layerCount = 3)
		{
			layerMetric_ = layerMetric;
			layerMetricSize_ = layerMetricSize;
			layerCount_ = layerCount;
		}
		
		/**
		 * 判断是否使用字符串类型分层。
		 * @return bool 返回true如果度量类型为字符串类型，否则返回false。
		 */
		bool isStringLayering() const { return sampleFrame_.getColumnType(layerMetric_) == Metric::TypeString; } 
		/// 计算每层总体数量
		void calcLayerPopulationSize();
		/// 根据总样本量和采样类型，计算每层样本数量; 
		void calcLayerSampleSizeByTotalSize(unsigned totalSampleSize, const string& metric, SampleConfigType type);
		/// 根据要求的最大误差计算每层的样本量, 并保存; 想要先设置好误差
		virtual bool calcSampleSize();

		/// 根据每层的样本量,计算每层的相对误差 ; 想要先设置好每层的样本量
		virtual bool calcRelativeError();

		const map<string, LayerInfo>& getLayerInfo() const { return layerInfos_; };
	
	private:
		// 下面两个参数决定了样框的分配; 后面再考虑多层的问题
		std::string			layerMetric_;			// 分层指标， 数值型根据layerCount_分层，字符型根据指标前几个layerMetricSize_字符来分层
		unsigned			layerMetricSize_ = 0;	// 0表示全部字符， 否则取前几个字符

		unsigned			layerCount_ = 3;		// 层数		
				
		StratifiedType		stratifiedType_;		// 分层方式，只对数值型指标分层有效

		SampleConfigType		sampleType_;		// 样本分配方式		
		map<string, LayerInfo>	layerInfos_;		// 层信息; key:数值型的为1/2/3这样的编号， 字符型的为字符串layerMetric_（layerMetricSize_）
	};
}
